import re
import json

__author__ = 'deadmoroz'

ID_RE = '[a-zA-Z][a-zA-Z0-9_]*'

ini = """
{
  "case_insensitive": true,
  "defaultMode": {
    "illegal": "[^\\\s]",
    "contains": [
      {
        "className": "comment",
        "begin": ";",
         "end": "$"
      },
      {
        "className": "title",
        "begin": "^\\\[",
         "end": "\\\]"
      },
      {
        "className": "setting",
        "begin": "^[a-z0-9_\\\[\\\]]+[ \\t]*=[ \\t]*",
        "end": "$",
        "contains": [
          {
            "className": "value",
            "endsWithParent": true,
            "keywords": {"on": 1, "off": 1, "true": 1, "false": 1, "yes": 1, "no": 1}
          }
        ]
      }
    ]
  }
}
"""

ini_lang = json.loads(ini)

languages = { "ini" : ini_lang}
modes = []
result = ""


def langRe(language, pattern):
    flags = re.M
    if "case_insensitive" in language:
        flags |= re.I

    return re.compile(pattern, flags)



def compileModes():
    def compileMode(mode, language, is_default):
        if "compiled" in mode and mode["compiled"]:
            return
        if not is_default:
            mode["beginRe"] = langRe(language, mode["begin"] if "begin" in mode else '\\B|\\b')
            if "end" not in mode and "endsWithParent" not in mode:
                mode["end"] = '\\B|\\b'
            if "end" in mode:
                mode["endRe"] = langRe(language, mode["end"])
        if "illegal" in mode:
            mode["illegalRe"] = langRe(language, mode["illegal"])
        if "keywords" in mode:
            mode["lexemsRe"] = langRe(language, mode["lexems"] if "lexems" in mode else ID_RE)
            keywords = mode["keywords"]
            for key in keywords:
                if keywords[key] is str:
                    mode["keywordGroups"] = {'keyword': keywords}
                else:
                    mode["keywordGroups"] = keywords

        if "contains" not in mode:
            mode["contains"] = []

        mode["compiled"] = True
        for childMode in mode["contains"]:
            compileMode(childMode, language, False)
        if "starts" in mode:
            compileMode(mode["starts"], language, False)
    for language in languages:
        compileMode(languages[language]["defaultMode"], language, True)


def highlight(language_name, value):
    global modes

    def subMode(lexeme, mode) :
        for childMode in mode["contains"]:
            if childMode["beginRe"].match(lexeme):
                return childMode
        return None


    language = languages[language_name]

    result = ""
    def endOfMode(mode_index, lexeme):
        if modes[mode_index].end and modes[mode_index].endRe.match(lexeme):
            return 1
        if modes[mode_index].endsWithParent:
            level = endOfMode(mode_index - 1, lexeme)
            return level + 1 if level else 0
        return 0

    def isIllegal(lexeme, mode) :
        return mode.illegalRe and mode.illegalRe.test(lexeme)

    def compileTerminators(mode, language):
        terminators = []

        for childMode in mode["contains"]:
            if "begin" in childMode:
                terminators.append(childMode["begin"])

        for mode in reversed(modes):
            if "end" in mode and mode["end"]:
                terminators.append(mode["end"])
            if "endsWithParent" not in mode or not mode["endsWithParent"]:
                break

        if "illegal" in mode and mode["illegal"]:
            terminators.append(mode["illegal"])

        return langRe(language, '(' + "|".join(terminators) + ')')


    def eatModeChunk(input, startIndex):
        mode = modes[-1]
        if "terminators" not in mode:
            mode["terminators"] = compileTerminators(mode, language)
        match = mode["terminators"].search(input, startIndex)
        if match:
            return [input[startIndex : match.start(0)], match.group(0), False]
        return [input[startIndex:], "", True]

    def keywordMatch(mode, keyword):
        if "case_insensitive" in language and language["case_insensitive"]:
            keyword = keyword.lower()
        if "keywordGroups" in mode:
            for className in mode["keywordGroups"]:
                if keyword in mode["keywordGroups"][className]:
                    return className
        return None


    def processKeywords(buffer, mode):
        result = ''
        index = 0;
        print mode

        lexeme = mode["lexemsRe"].search(buffer, index) if "lexemsRe" in mode else None
        while lexeme:

            result += buffer[index: lexeme.start(0)]

            lexemeBody = lexeme.group(0)

            keyword = keywordMatch(mode, lexemeBody)
            if keyword:
                result += '<span class="'+ keyword +'">' + lexemeBody + '</span>'
            else:
                result += lexemeBody

            index = lexeme.end(0)

            lexeme = mode["lexemsRe"].search(buffer, index)

        result += buffer[index:]
        return result

    def processBuffer(buffer, mode):
        if "subLanguage" in mode and languages[mode["subLanguage"]]:
            return highlight(mode["subLanguage"], buffer)
        return processKeywords(buffer, mode)

    def startNewMode(mode, lexeme):
        global result
        
        markup = ""
        if "className" in mode:
            markup = '<span class="' + mode["className"] + '">'

        if "returnBegin" in mode and mode["returnBegin"]:
            result += markup
            mode["buffer"] = ''
        elif "excludeBegin" in mode and mode["excludeBegin"]:
            result += lexeme + markup
            mode["buffer"] = ''
        else:
            result += markup
            mode["buffer"] = lexeme

        modes.append(mode)


    def processModeInfo(buffer, lexeme, end):
        global result

        current_mode = modes[-1]
        if end:
            result += processBuffer(current_mode["buffer"] + buffer, current_mode)
            return False

        new_mode = subMode(lexeme, current_mode)
        if new_mode:
            result += processBuffer(current_mode["buffer"] + buffer, current_mode)
            startNewMode(new_mode, lexeme)
            return new_mode["returnBegin"] if "returnBegin" in new_mode else False


        end_level = endOfMode(len(modes) - 1, lexeme)
        if end_level:
            markup = ''
            if current_mode.className:
                markup = '</span>'
            if current_mode.returnEnd:
                result += processBuffer(current_mode["buffer"] + buffer, current_mode) + markup
            elif current_mode.excludeEnd:
                result += processBuffer(current_mode["buffer"] + buffer, current_mode) + markup + lexeme
            else:
                result += processBuffer(current_mode["buffer"] + buffer + lexeme, current_mode) + markup
            while end_level > 1:
                markup = ""
                modes.pop()
                if("className" in modes[-1]):
                    markup = '</span>'
                result += markup
                end_level = end_level - 1

            last_ended_mode = modes.pop()
            modes[-1]["buffer"] = ''
            if "starts" in last_ended_mode and last_ended_mode["starts"]:
                startNewMode(last_ended_mode["starts"], '')
            return current_mode["returnEnd"]

        if isIllegal(lexeme, current_mode):
            raise Exception('Illegal')

    compileModes()
    modes = [language["defaultMode"]]
    index = 0
    language["defaultMode"]["buffer"] = ''
    while True:
        mode_info = eatModeChunk(input, index)
        return_lexeme = processModeInfo(mode_info[0], mode_info[1], mode_info[2])
        index += len(mode_info[0])
        if not return_lexeme:
            index += len(mode_info[1])
        if not mode_info[2]:
            break

    if len(modes) > 1:
        raise Exception('Illegal')

    return result

ini_body = """
            [info]
            cookie=CatTools
            version=3
            author=SolarWinds
            """









